package com.tyylab.message.distribute.distribute;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.tyylab.message.distribute.extapi.IConfigProvider;
import com.tyylab.message.distribute.model.MessageChannelConfig;
import com.tyylab.message.distribute.model.MessageDistribute;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ChannelManager implements IChannelGetter, IChannelBuilder {
    @Autowired
    IConfigProvider configProvider;

    Timestamp lastRead = null;
    Timestamp lastReadChannelConfig = null;

    // 最终的channal存储结果（Clz->Channels）
    Map<String, Set<String>> clzChannelMap;
    // 从数据库读取的分发配置
    List<MessageDistribute> messageDistributes;
    // 全局分发的Channel（分发所有的事件）
    Set<String> globalChannels;
    // 按包分发的channel
    Map<String,Set<String>> packageChannelMap;

    //// exclude
    // clz
    Map<String, Set<String>> excludeClzChannelMap;
    // global
    Set<String> excludeGlobalChannels;

    // 按包分发的channel
    Map<String,Set<String>> excludePackageChannelMap;

    Map<String, Boolean> channelSupportUnique =Maps.newLinkedHashMap();

    List<MessageChannelConfig> messageChannelConfigs;

    @Autowired
    @Override
    public void build(){
        if (configProvider.needUpdate(lastRead)){
            Timestamp lt = lastRead;
            this.lastRead = getNow();
            try {
                List<MessageDistribute> messageDistributes = configProvider.selectAll();
                build(messageDistributes);
            }
            catch (Exception e){
                log.error(e.getMessage(),e);
                lastRead = lt;
            }
        }

        if (configProvider.needUpdateChannelConfig(lastReadChannelConfig)){
            Timestamp lt = lastReadChannelConfig;
            this.lastReadChannelConfig = getNow();
            try {
                List<MessageChannelConfig> mes = configProvider.selectAllMessageChannelConfig();
                buildMessageChannelConfig(mes);
            }
            catch (Exception e){
                log.error(e.getMessage(),e);
                lastReadChannelConfig = lt;
            }
        }

    }

    void buildMessageChannelConfig(List<MessageChannelConfig> messageChannelConfigs){
        if (null != messageChannelConfigs && !messageChannelConfigs.isEmpty()){
            this.messageChannelConfigs = messageChannelConfigs;
            rebuildMessageChannel();
        }
        else{
            log.warn("messageDistributes is empty.");
        }
    }

    @Override
    public void build(List<MessageDistribute> messageDistributes){
        if (null != messageDistributes && !messageDistributes.isEmpty()){
            this.messageDistributes = messageDistributes;
            rebuild();
        }
        else{
            log.warn("messageDistributes is empty.");
        }
    }

    private void rebuild(){
        // 先创建exclude，再创建include，这样可以排除exclude
        //
        Set<String> tmpExcludeGlobalChannels = Sets.newHashSet();
        Map<String,Set<String>> tmpExcludePackageChannelMap = Maps.newConcurrentMap();
        Map<String,Set<String>> tmpExcludeClzChannelMap = Maps.newConcurrentMap();
        rebuildExclude(tmpExcludeGlobalChannels,tmpExcludePackageChannelMap,tmpExcludeClzChannelMap);

        this.excludeGlobalChannels = tmpExcludeGlobalChannels;
        this.excludePackageChannelMap = tmpExcludePackageChannelMap;
        this.excludeClzChannelMap = tmpExcludeClzChannelMap;

        //
        Set<String> tmpglobalChannels = Sets.newHashSet();
        Map<String,Set<String>> tmppackageChannelMap = Maps.newConcurrentMap();
        Map<String,Set<String>> tmpclzChannelMap = Maps.newConcurrentMap();
        rebuildInclude(tmpglobalChannels, tmppackageChannelMap, tmpclzChannelMap);

        this.globalChannels = tmpglobalChannels;
        this.packageChannelMap = tmppackageChannelMap;
        this.clzChannelMap = tmpclzChannelMap;


        log.info("已更新全部channel");
    }

    private void rebuildMessageChannel(){
        if (null == this.messageChannelConfigs || this.messageChannelConfigs.isEmpty()){
            this.channelSupportUnique = Maps.newHashMap();
        }
        else{
            Map<String,Boolean> map = Maps.newHashMap();
            for (MessageChannelConfig co :
                    this.messageChannelConfigs) {
                map.put(co.getChannel(),co.getIsSupportUnique()>0);
            }
            this.channelSupportUnique = map;
        }
        log.info("已更新全部channel config");
    }

    private void rebuildInclude(Set<String> tmpglobalChannels, Map<String, Set<String>> tmppackageChannelMap, Map<String, Set<String>> tmpclzChannelMap) {
        for (MessageDistribute m :
                messageDistributes.stream()
                .filter(mds-> {return StringUtils.equalsIgnoreCase("Include",mds.getOperation());})
                .collect(Collectors.toList())
        ) {
            if (StringUtils.isBlank(m.getChannel())){
                log.warn("channel is emppty =>" + JSON.toJSONString(m));
                continue;
            }
            if(StringUtils.equalsIgnoreCase("Global",m.getScope()) ){
                tmpglobalChannels.add(m.getChannel().trim());
            }
            else if (StringUtils.equalsIgnoreCase("Package",m.getScope())){
                addMapVal(tmppackageChannelMap,m.getName().trim(),m.getChannel().trim());
            }
            else if (StringUtils.equalsIgnoreCase("Class",m.getScope())){
                addMapVal(tmpclzChannelMap,m.getName().trim(),m.getChannel().trim());
            }
        }
        //补充 global/package channel 到clz中

        for (Map.Entry<String,Set<String>> entry:
                tmpclzChannelMap.entrySet()) {
            String clzName = entry.getKey();
            Set<String> channels = entry.getValue();
            //add global
            if (!tmpglobalChannels.isEmpty()){
                channels.addAll(tmpglobalChannels);
            }
            //add package
            for (Map.Entry<String,Set<String>> packageEntry:
                    tmppackageChannelMap.entrySet()
            ) {
                if (clzName.startsWith(packageEntry.getKey())){
                    channels.addAll(packageEntry.getValue());
                }
            }

            // 去除exclude channel
            Set<String> excludeChannels = getExcludeChannels(clzName);
            channels.removeAll(excludeChannels);
        }
    }

    /**
     * 获取排除的channel
     * @param messageClz
     * @return
     */
    private Set<String> getExcludeChannels(String messageClz){
        Set<String> excludeChannels = Sets.newHashSet();
        excludeChannels.addAll(excludeGlobalChannels);
        if (excludeClzChannelMap.containsKey(messageClz)){
            excludeChannels.addAll(excludeClzChannelMap.get(messageClz));
        }
        for (Map.Entry<String,Set<String>> packageEntry:
                    excludePackageChannelMap.entrySet()
            ) {
                if (messageClz.startsWith(packageEntry.getKey())){
                    excludeChannels.addAll(packageEntry.getValue());
                }
        }
        return excludeChannels;
    }

    private void rebuildExclude(Set<String> tmpExcludeGlobalChannels, Map<String, Set<String>> tmpExcludePackageChannelMap, Map<String, Set<String>> tmpExcludeClzChannelMap){
        for (MessageDistribute m :
                messageDistributes.stream()
                        .filter(mds-> {return StringUtils.equalsIgnoreCase("Exclude",mds.getOperation());})
                        .collect(Collectors.toList())
        ) {
            if (StringUtils.isBlank(m.getChannel())){
                log.warn("channel is emppty =>" + JSON.toJSONString(m));
                continue;
            }
            if(StringUtils.equalsIgnoreCase("Global",m.getScope()) ){
                tmpExcludeGlobalChannels.add(m.getChannel().trim());
            }
            else if (StringUtils.equalsIgnoreCase("Package",m.getScope())){
                addMapVal(tmpExcludePackageChannelMap,m.getName().trim(),m.getChannel().trim());
            }
            else if (StringUtils.equalsIgnoreCase("Class",m.getScope())){
                addMapVal(tmpExcludeClzChannelMap,m.getName().trim(),m.getChannel().trim());
            }
        }
//        //补充 global/package channel 到clz中
//
//        for (Map.Entry<String,Set<String>> entry:
//                tmpExcludeClzChannelMap.entrySet()) {
//            String clzName = entry.getKey();
//            Set<String> channels = entry.getValue();
//            //add global
//            if (!tmpExcludeGlobalChannels.isEmpty()){
//                channels.addAll(tmpExcludeGlobalChannels);
//            }
//            //add package
//            for (Map.Entry<String,Set<String>> packageEntry:
//                    tmppackageChannelMap.entrySet()
//            ) {
//                if (clzName.startsWith(packageEntry.getKey())){
//                    channels.addAll(packageEntry.getValue());
//                }
//            }
//        }
    }

    @Override
    public Collection<String> getTargetChannel(String messageClz) {
        if (clzChannelMap.containsKey(messageClz)){
            return clzChannelMap.get(messageClz);
        }
        else{
            Set<String> channels = Sets.newHashSet();
            //add global
            if (!globalChannels.isEmpty()){
                channels.addAll(globalChannels);
            }
            //add package
            for (Map.Entry<String,Set<String>> packageEntry:
                    packageChannelMap.entrySet()
            ) {
                if (messageClz.startsWith(packageEntry.getKey())){
                    channels.addAll(packageEntry.getValue());
                }
            }

            // 去除exclude channel
            Set<String> excludeChannels = getExcludeChannels(messageClz);
            channels.removeAll(excludeChannels);

            clzChannelMap.put(messageClz,channels);
            return channels;
        }
    }

    @Override
    public Boolean isSupportUnique(String channel) {
        return channelSupportUnique.getOrDefault(channel, false);
    }

    private <K,V> void addMapVal(Map<K,Set<V>> map,K k, V v){
        Set<V> vSet;
        if (map.containsKey(k)){
            vSet = map.get(k);
        }
        else{
            vSet = Sets.newHashSet();
            map.put(k,vSet);
        }
        vSet.add(v);
    }


    private static Timestamp getNow(){
        return Timestamp.valueOf(LocalDateTime.now());
    }
}
